﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.Collections.Generic;
using System.ComponentModel;
#if USE_HYPER
using Hyper.ComponentModel;
#endif

namespace BrainTechLLC.ThreadSafeObjects
{
    /// <summary>
    /// A thread safe list & lookup allows multiple threads to efficiently work
    /// with both a single list for Adding, Removing, and Searching for lists of items by key
    /// This version is safe for collections that frequently remove/add items
    /// </summary>
#if NO_SILVERLIGHT
    [TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
#endif
    [Serializable]
    [DataContract]
    public class SafeThreadSafeListAndLookup<TIndex, TListType> :
       Lockable, IMultipleItems<TListType>,
       ISupportsFindMany<TIndex, TListType>,
       ISupportsCount,
       IDictionary<TIndex, List<TListType>>,
       ISupportsAddRemoveClear<TIndex, TListType> where TListType : class
    {
        /// <summary>
        /// Stores a running count of items
        /// </summary>
        [DataMember]
        protected int _count;

        /// <summary>
        /// A full list of items in the collection
        /// </summary>
        [DataMember]
        protected List<TListType> _list = new List<TListType>();

        /// <summary>
        /// A dictionary of list of items, use for fast lookup by index/key
        /// </summary>
        [DataMember]
        protected Dictionary<TIndex, List<TListType>> Lookup = new Dictionary<TIndex, List<TListType>>();

        /// <summary>
        /// Stores a common (cached) empty list
        /// </summary>
#if NO_SILVERLIGHT
        [NonSerialized, XmlIgnore]
#endif
        protected readonly static List<TListType> _emptyList = new List<TListType>();

        public object Clone() { return MakeCopy(); }
        protected ThreadSafeListAndLookup<TIndex, TListType> MakeCopy() { return null; }

        /// <summary>
        /// Returns an array containing all items in the collection
        /// </summary>
        public TListType[] ArrayOfItems { get { return AllItems.ToArray(); } }

        /// <summary>
        /// Returns a mostly-accurate count of items currently in the collection
        /// </summary>
        public int Count { get { return _count; } }

        /// <summary>
        /// Adds an item to the collection
        /// </summary>
        /// <returns>True if the item was already in the collection, otherwise false</returns>
        public virtual bool Add(TIndex key, TListType item)
        {
            List<TListType> list;
            bool found = true;

            AquireLock();
            {
                if (!Lookup.TryGetValue(key, out list))
                {
                    list = new List<TListType>();
                    Lookup.Add(key, list);
                    found = false;
                }

                if (!list.Contains(item))
                {
                    list.Add(item);
                    _list.Add(item);
                    _count++;
                    found = false;
                }
            }
            ReleaseLock();

            return found;
        }

        /// <summary>
        /// Returns a snapshot/list copy of all items in the collection
        /// </summary>
        public List<TListType> AllItems
        {
            get
            {
                List<TListType> list = new List<TListType>(_count);

                AquireLock();
                {
                    list.AddRange(_list);
                }
                ReleaseLock();

                return list;
            }
        }

        /// <summary>
        /// Finds the first item in the collection with the given key
        /// </summary>
        public virtual TListType FindSingle(TIndex key)
        {
            TListType item = null;
            List<TListType> list;

            AquireLock();
            {
                if (Lookup.TryGetValue(key, out list))
                {
                    if (list.Count > 0)
                    {
                        item = list[0];
                    }
                }
            }
            ReleaseLock();

            return item;
        }

        /// <summary>
        /// Removes an item from the collection
        /// </summary>
        /// <returns>True if the item was removed, false if the item could not be found in the collection</returns>
        public bool Remove(TIndex key, TListType item)
        {
            if (!Lookup.ContainsKey(key))
                return false;

            List<TListType> list;

            AquireLock();
            {
                if (Lookup.TryGetValue(key, out list))
                {
                    list.Remove(item);
                }
                if (_list.Remove(item))
                {
                    _count--;
                }
            }
            ReleaseLock();

            return true;
        }

        /// <summary>
        /// Removes a single item from the collection based on a key and a match delegate
        /// </summary>
        public TListType Remove(TIndex key, Func<TListType, bool> match)
        {
            if (!Lookup.ContainsKey(key))
                return null;

            List<TListType> list;
            TListType found = null;

            AquireLock();
            {
                if (Lookup.TryGetValue(key, out list))
                {
                    for (int n = 0; n < list.Count; n++)
                    {
                        TListType item = list[n];
                        if (match(item))
                        {
                            list.RemoveAt(n);
                            break;
                        }
                    }
                }

                for (int n = 0; n < _list.Count; n++)
                {
                    TListType item = _list[n];
                    if (match(item))
                    {
                        found = item;
                        _list.RemoveAt(n);
                        _count--;
                        break;
                    }
                }
            }
            ReleaseLock();

            return found;
        }

        /// <summary>
        /// Clears all items from the collection
        /// </summary>
        public void Clear()
        {
            AquireLock();
            {
                _list.Clear();
                Lookup.Clear();
                _count = 0;
            }
            ReleaseLock();
        }

        /// <summary>
        /// Finds all items in the collection with the given key
        /// </summary>
        public List<TListType> Find(TIndex key)
        {
            List<TListType> list;
            if (!Lookup.ContainsKey(key))
            {
                return _emptyList;
            }
            else
            {
                AquireLock();
                {
                    if (Lookup.TryGetValue(key, out list))
                    {
                        list = new List<TListType>(list);
                    }
                    else
                    {
                        list = _emptyList;
                    }
                }
                ReleaseLock();

                return list;
            }
        }

        /// <summary>
        /// Finds a single item in the collection with the given key, using a match delegate
        /// </summary>
        public TListType Find(TIndex key, Func<TListType, bool> match)
        {
            List<TListType> list;
            if (!Lookup.ContainsKey(key))
                return null;

            TListType item = null;

            AquireLock();
            {
                if (Lookup.TryGetValue(key, out list))
                {
                    for (int n = 0; n < list.Count; n++)
                    {
                        TListType find = list[n];
                        if (match(find))
                        {
                            item = find;
                            break;
                        }
                    }
                }
                ReleaseLock();

                return item;
            }
        }

        #region IDictionary<TIndex,List<TListType>> Members

        public void Add(TIndex key, List<TListType> items)
        {
            foreach (TListType o in items)
                Add(key, o);
        }

        public bool ContainsKey(TIndex key)
        {
            return Lookup.ContainsKey(key);
        }

        public ICollection<TIndex> Keys
        {
            get { return Lookup.Keys; }
        }

        public bool Remove(TIndex key)
        {
            return this.Remove(key);
        }

        public bool TryGetValue(TIndex key, out List<TListType> value)
        {
            return Lookup.TryGetValue(key, out value);
        }

        public ICollection<List<TListType>> Values
        {
            get { return Lookup.Values; }
        }

        public List<TListType> this[TIndex key]
        {
            get
            {
                return Find(key);
            }
            set
            {
                // TODO - remove existing?
                Add(key, value);
            }
        }

        #endregion

        #region ICollection<KeyValuePair<TIndex,List<TListType>>> Members

        public void Add(KeyValuePair<TIndex, List<TListType>> item)
        {
            foreach (TListType o in item.Value)
                Add(item.Key, o);
        }

        public bool Contains(KeyValuePair<TIndex, List<TListType>> item)
        {
            return Lookup.ContainsKey(item.Key);
        }

        public void CopyTo(KeyValuePair<TIndex, List<TListType>>[] array, int arrayIndex)
        {
            AquireLock();
            {
                foreach (KeyValuePair<TIndex, List<TListType>> item in Lookup)
                {
                    array[arrayIndex++] = item;
                }
            }
            ReleaseLock();
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public bool Remove(KeyValuePair<TIndex, List<TListType>> item)
        {
            return Remove(item.Key);
        }

        #endregion

        #region IEnumerable<KeyValuePair<TIndex,List<TListType>>> Members

        public IEnumerator<KeyValuePair<TIndex, List<TListType>>> GetEnumerator()
        {
            return Lookup.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return Lookup.GetEnumerator();
        }

        #endregion
    }
}